use actix_web::web::Data;
use chrono::Local;
use sea_orm::{
    prelude::Expr,
    ActiveModelTrait,
    ActiveValue::{NotSet, Set},
    ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QuerySelect, TransactionTrait,
};

use crate::{
    common::{
        error::ApiError,
        response::ApiResponse,
        types::{ApiListResult, ApiResult},
    },
    config::{auth::AuthInfo, state::AppState},
    models::{
        entity::{
            perm,
            prelude::{Perm, RolePerm, UserRole},
            role_perm, user_role,
        },
        request::{
            common_request::IdRequest,
            menu_req::{MenuCreate, MenuUpdate},
        },
        response::menu_res::{MenuDetail, MenuListInfo},
    },
};

pub async fn create(
    state: Data<AppState>,
    req: MenuCreate,
    auth_info: AuthInfo,
) -> ApiResult<bool> {
    let now = Local::now().naive_local();
    let perm = perm::ActiveModel {
        id: NotSet,
        name: Set(req.name),
        desc: Set(req.desc),
        parent_id: Set(req.parent_id),
        path: Set(req.path),
        r#type: Set(0),
        order: Set(req.order),
        create_at: Set(now),
        update_at: Set(now),
        create_by: Set(auth_info.user_id),
        update_by: Set(auth_info.user_id),
        deleted: Set(false),
    };

    perm.insert(&state.db).await?;

    Ok(ApiResponse::ok(true))
}

pub async fn delete(state: Data<AppState>, req: IdRequest, auth_info: AuthInfo) -> ApiResult<bool> {
    let is_root = auth_info.role_ids.contains(&1);

    if !is_root {
        let role_ids: Vec<i32> = UserRole::find()
            .select_only()
            .column(user_role::Column::RoleId)
            .filter(user_role::Column::UserId.eq(auth_info.user_id))
            .filter(user_role::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        let perm_ids: Vec<i32> = RolePerm::find()
            .select_only()
            .column(role_perm::Column::PermId)
            .filter(role_perm::Column::RoleId.is_in(role_ids))
            .filter(role_perm::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        if !perm_ids.contains(&req.id) {
            return Err(ApiError::Forbidden("无权限"));
        }
    }

    let perm = Perm::find_by_id(req.id)
        .filter(perm::Column::Type.eq(0))
        .filter(perm::Column::Deleted.eq(false))
        .one(&state.db)
        .await?
        .ok_or(ApiError::NotFound("菜单不存在"))?;

    let now = Local::now().naive_local();

    let txn = state.db.begin().await?;

    let mut perm = perm.into_active_model();
    perm.deleted = Set(true);
    perm.update_at = Set(now);
    perm.update_by = Set(auth_info.user_id);
    perm.update(&txn).await?;

    RolePerm::update_many()
        .col_expr(role_perm::Column::Deleted, Expr::value(true))
        .col_expr(role_perm::Column::UpdateAt, Expr::value(now))
        .col_expr(role_perm::Column::UpdateBy, Expr::value(auth_info.user_id))
        .filter(role_perm::Column::PermId.eq(req.id))
        .exec(&txn)
        .await?;

    txn.commit().await?;

    Ok(ApiResponse::ok(true))
}

pub async fn update(
    state: Data<AppState>,
    req: MenuUpdate,
    auth_info: AuthInfo,
) -> ApiResult<bool> {
    let is_root = auth_info.role_ids.contains(&1);

    if !is_root {
        let role_ids: Vec<i32> = UserRole::find()
            .select_only()
            .column(user_role::Column::RoleId)
            .filter(user_role::Column::UserId.eq(auth_info.user_id))
            .filter(user_role::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        let perm_ids: Vec<i32> = RolePerm::find()
            .select_only()
            .column(role_perm::Column::PermId)
            .filter(role_perm::Column::RoleId.is_in(role_ids))
            .filter(role_perm::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        if !perm_ids.contains(&req.id) {
            return Err(ApiError::Forbidden("无权限"));
        }
    }

    let perm = Perm::find_by_id(req.id)
        .filter(perm::Column::Type.eq(0))
        .filter(perm::Column::Deleted.eq(false))
        .one(&state.db)
        .await?
        .ok_or(ApiError::NotFound("菜单不存在"))?;

    let now = Local::now().naive_local();

    let txn = state.db.begin().await?;

    let mut perm = perm.into_active_model();
    perm.name = Set(req.name);
    perm.desc = Set(req.desc);
    perm.parent_id = Set(req.parent_id);
    perm.order = Set(req.order);
    perm.path = Set(req.path);
    perm.update_at = Set(now);
    perm.update_by = Set(auth_info.user_id);

    perm.update(&txn).await?;

    txn.commit().await?;

    Ok(ApiResponse::ok(true))
}

pub async fn detail(
    state: Data<AppState>,
    req: IdRequest,
    auth_info: AuthInfo,
) -> ApiResult<MenuDetail> {
    let is_root = auth_info.role_ids.contains(&1);

    if !is_root {
        let role_ids: Vec<i32> = UserRole::find()
            .select_only()
            .column(user_role::Column::RoleId)
            .filter(user_role::Column::UserId.eq(auth_info.user_id))
            .filter(user_role::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        let perm_ids: Vec<i32> = RolePerm::find()
            .select_only()
            .column(role_perm::Column::PermId)
            .filter(role_perm::Column::RoleId.is_in(role_ids))
            .filter(role_perm::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        if !perm_ids.contains(&req.id) {
            return Err(ApiError::Forbidden("无权限"));
        }
    }

    let perm = Perm::find_by_id(req.id)
        .filter(perm::Column::Deleted.eq(false))
        .filter(perm::Column::Type.eq(0))
        .one(&state.db)
        .await?
        .ok_or(ApiError::NotFound("菜单不存在"))?
        .into();

    Ok(ApiResponse::ok(perm))
}

pub async fn list(state: Data<AppState>, auth_info: AuthInfo) -> ApiListResult<MenuListInfo> {
    let is_root = auth_info.role_ids.contains(&1);

    let mut select = Perm::find()
        .filter(perm::Column::Deleted.eq(false))
        .filter(perm::Column::Type.eq(0));

    if !is_root {
        let role_ids: Vec<i32> = UserRole::find()
            .select_only()
            .column(user_role::Column::RoleId)
            .filter(user_role::Column::UserId.eq(auth_info.user_id))
            .filter(user_role::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        let perm_ids: Vec<i32> = RolePerm::find()
            .select_only()
            .column(role_perm::Column::PermId)
            .filter(role_perm::Column::RoleId.is_in(role_ids))
            .filter(role_perm::Column::Deleted.eq(false))
            .into_tuple()
            .all(&state.db)
            .await?;

        select = select.filter(perm::Column::Id.is_in(perm_ids));
    }

    let perm_list = select.all(&state.db).await?;

    let mut menu_list: Vec<MenuListInfo> = MenuListInfo::build_tree(perm_list);

    menu_list.sort_by_key(|m| m.order);

    Ok(ApiResponse::ok(menu_list))
}
